//std headers
#include <iostream>
#include <vector>
#include <string>
#include <exception>

//boost headers
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/optional.hpp>
#include <boost/shared_ptr.hpp>

//project headers
#include <SingletonHolder.h>
#include "Options.h"
#include "./Psqldb/IDatabase.h"
#include "./Psqldb/Psql.h"
#include "./Tasks/TaskDispatcher.h"
#include "./Tasks/TaskConvert.h"
#include "./Tasks/TaskAlive.h"
#include "./Tasks/TaskHighlight.h"
#include "./Server/Server.h"
#include "ConvertorManager.h"


int main(int argc,char **argv)
{
	std::string myLocation(argv[0]);
	std::size_t index = -1;
#ifdef _WIN32
	index = myLocation.find_last_of('\\');
#else
	index = myLocation.find_last_of('/');
#endif
	
	if(index !=std::string::npos)
	{
		myLocation = myLocation.substr(0,index+1);
	}

	SingletonHolder::Instance("mconvertor").WriteEx("Starting application...");

		Options appOptions(myLocation);	//otvorime config.ini

		if(argc != 2)
		{
			std::cout << "----------Zli pocet parametrov-------------" << std::endl;
			std::cout << "pouzitie: convertor [heslo_do_db]" <<std::endl;
			std::cout << "-------------------------------------------" << std::endl;
			return -1;
		}

		try
		{
			std::string dbConstring = DB::Psql::CreateConnectionString( appOptions.GetOne<std::string>("database","address"),
																		appOptions.GetOne<std::string>("database","port"),
																		appOptions.GetOne<std::string>("database","database_name"),
																		appOptions.GetOne<std::string>("database","username"),
																		argv[1]);
			
			//nahrame vsetky pluginy na konverziu ktore su zadane v configuracnom
			//subore, na spravu pluginov urcenych na konverziu sa pouziva
			//vyhradne ConvertorManager
			ConvertorManager *aConvertorMgr = new ConvertorManager(appOptions.GetAll<std::string>("plugins","convertor"));

			
			//pokusime sa pripojit na databazu ak nepodarisa, tak zhodime celu app
			//bez db spojenia nema vyznam spustat app
			DB::IDatabase *aDb = new DB::Psql(dbConstring);

			
			//vytvorime dispetcer uloh ktory na zaklade prijateho kodu ulohy, vykona vsetky
			//potrebne inicializacie a spusti danu ulohu na zaklade kodu
			//Dany dispatcher je pre uholy pracujuce nad DataContainer
			Tasks::TaskDispatcher<Data> tasksDispatcher;

			Tasks::ITask<Data> *aConvertorTask = new Tasks::TaskConvert(aDb,aConvertorMgr,
																								   appOptions.GetOne<std::string>("client","address"),
																								   appOptions.GetOne<std::string>("client","port"));

			Tasks::ITask<Data> *aHighlightTask = new Tasks::TaskHighlight(aDb);

			Tasks::ITask<Data> *aAliveTask = new Tasks::TaskAlive();
			//Zaregistrujeme vsetky podporovane ulohy ktore podporuje tento server
			//do Dispecera uloh
			tasksDispatcher.Register('c',aConvertorTask);		//ulohu konverzie zaregistrujeme pod kodom c(onvert)
			tasksDispatcher.Register('h',aHighlightTask);		//ulohu zvyraznovania zaregistrujeme pod kodom h(ighlight)
			tasksDispatcher.Register('a',aAliveTask);

			//ak mame nahrate pluginy a sme uspesne pripojeny na databazu tak
			//spustime server na danom port-e a s danym poctom threadov
			Server aServer(appOptions.GetOne<std::string>("server","port"),appOptions.GetOne<std::size_t>("server","threads"));
			boost::thread server_th(boost::bind(&Server::Run,&aServer));

			SingletonHolder::Instance().WriteEx("----------------------Up & Running------------------");

			while(1)		
			{
					boost::optional<boost::shared_ptr<Data> > aReceivedContainer = aServer.GetReceived();
					if(aReceivedContainer.is_initialized())
					{
						boost::shared_ptr<Data> aReceived = aReceivedContainer.get();

						//ulohy vykonavame asynchronne
						if(!tasksDispatcher.ExecuteAsync(aReceived->theContainer.GetCommand(),(*aReceived)))
						{
							if(aReceived->theConnection)
							{
								aReceived->theConnection->close();
								delete aReceived->theConnection;
							}
						}
					}
			}
			
			aServer.Stop();
			server_th.join();
		}
		catch(std::exception ex)
		{
			std::string exmsg = ex.what();
			SingletonHolder::Instance().WriteEx("Fatal exception! Application shutdown:" + exmsg);
		}



		/*
	    Tasks::TaskDispatcher<PComunicator::DataContainer> aDispatcher;
		
		PComunicator::DataContainer container;
		PComunicator::DataContainer container2;


		aDispatcher.ExecuteAsync('c',container);
		aDispatcher.ExecuteAsync('c',container2);
		*/
		/*Options myOptions;

		std::string db_adress = myOptions.GetOne<std::string>("database","address");
		int db_port = myOptions.GetOne<int>("database","port");
		int sv_port = myOptions.GetOne<int>("server","port");
		std::vector<std::string> plugins = myOptions.GetAll<std::string>("plugins","convertor");

		sv_port = myOptions.GetOne<int>("server","port");
		*/

		

		/*
		PComunicator::PFileInfo aInfo("./Plugins/converter_mock.shl");
		PluginSystem::PluginConvertor *myPlugin = new PluginSystem::PluginConvertor("./Plugins/converter_mock.shl");

		bool result  = myPlugin->Open(aInfo);
		std::cout << myPlugin->Version() << std::endl;

		std::wstring awstring = myPlugin->Read();

		delete myPlugin;
		*/
		/*
		std::vector<std::string> myPlugins;
		myPlugins.push_back("./Plugins/converter_mock.shl");
		myPlugins.push_back("./Plugins/this_not_exists.shl");

		ConvertorManager aManager(myPlugins);

		PluginSystem::IConverter *aConvertor = aManager.Get("return");
		if(aConvertor!=NULL)
		{
			std::wstring result = aConvertor->Read();
		}
		*/

		return 0;		

}